package com.goldman.http.common.s2s;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.goldman.http.common.s2s.interceptor.CustomRequestInterceptor;
import com.goldman.http.common.s2s.model.ResponseData;
import com.goldman.http.common.s2s.model.Status;
import com.goldman.http.common.s2s.model.WebThreadContext;
import com.goldman.http.common.s2s.util.JsonUtil;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.http.util.TextUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class S2SHttp {

    private static final String METHOD_GET = "GET";
    private static final String METHOD_POST = "POST";

    private static CustomRequestInterceptor customInterceptor = new CustomRequestInterceptor();

    private static final int TIMEOUT = 60 * 1000;

    public static <D> S2SResponse<D> get(String url) {
        return doGet(url, null);
    }

    public static <D> S2SResponse<D> get(String url, S2SOption s2SOption) {
        return doGet(url, s2SOption);
    }

    private static <D> S2SResponse<D> doGet(String url, S2SOption s2SOption) {
        if (s2SOption == null) {
            s2SOption = S2SOption.builder().build();
        }
        if (s2SOption.getContentType() != null) {
            Map<String, String> headers = s2SOption.getHeaders() == null ? new HashMap<>() : s2SOption.getHeaders();
            headers.put("Content-Type", s2SOption.getContentType().getContent());
            s2SOption.setHeaders(headers);
        }
        long beginTime = System.currentTimeMillis();
        S2SResponse<D> s2SResponse = callGet(url, s2SOption);
        if (s2SResponse.getException() != null) {
            s2SResponse.setResponseData(ResponseData.sysError(s2SResponse.getExceptionPoint()));
        }
        s2SResponse.setExecuteTime(System.currentTimeMillis() - beginTime);
        logAccess(METHOD_GET, url, s2SOption, null, s2SResponse);
        return s2SResponse;
    }

    private static <D> S2SResponse<D> callGet(String url, S2SOption s2SOption) {
        // 构建URI
        URIBuilder uriBuilder;
        try {
            uriBuilder = new URIBuilder(url);
        } catch (URISyntaxException e) {
            return S2SResponse.<D>builder().httpStatus(-1).
                    exception(e).exceptionPoint("new URIBuilder occur URISyntaxException when callGet").build();
        }
        // 设置参数
        if (s2SOption.getParams() != null && s2SOption.getParams().keySet().size() > 0) {
            for (String key : s2SOption.getParams().keySet()) {
                uriBuilder.setParameter(key, s2SOption.getParams().get(key));
            }
        }
        // 构建HttpGet
        HttpGet httpGet = null;
        try {
            httpGet = new HttpGet(uriBuilder.build());
        } catch (URISyntaxException e) {
            return S2SResponse.<D>builder().httpStatus(-1).
                    exception(e).exceptionPoint("new HttpGet occur URISyntaxException when callGet").build();
        }
        // 设置头
        if (s2SOption.getHeaders() != null && s2SOption.getHeaders().size() > 0) {
            for (String key : s2SOption.getHeaders().keySet()) {
                httpGet.setHeader(key, s2SOption.getHeaders().get(key));
            }
        }
        // 请求配置：超时等。
        int timeout = s2SOption.getTimeout() > 0 ? s2SOption.getTimeout() : TIMEOUT;
        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(timeout).build();
        httpGet.setConfig(requestConfig);
        // 执行请求
        CloseableHttpClient httpClient = HttpClients.custom()
                .addInterceptorFirst(customInterceptor)
                .build();
        CloseableHttpResponse httpResponse = null;
        try {
            httpResponse = httpClient.execute(httpGet);
        } catch (IOException e) {
            close(httpClient, null);
            return S2SResponse.<D>builder().httpStatus(-1).
                    exception(e).exceptionPoint("httpClient.execute occur IOException when callGet").build();
        }
        // 获取响应实体
        HttpEntity entity = httpResponse.getEntity();
        String responseRawData = "";
        try {
            responseRawData = EntityUtils.toString(entity, "utf-8");
        } catch (IOException e) {
            close(httpClient, httpResponse);
            return S2SResponse.<D>builder().httpStatus(httpResponse.getStatusLine().getStatusCode()).
                    exception(e).exceptionPoint("EntityUtils.toString occur IOException when callGet").build();
        }
        // 反序列化响应体
        ResponseData<D> responseData;
        try {
            responseData = parseResponseData(s2SOption, responseRawData, httpResponse);
        } catch (Exception e) {
            close(httpClient, httpResponse);
            return S2SResponse.<D>builder().httpStatus(httpResponse.getStatusLine().getStatusCode()).
                    exception(e).exceptionPoint("parseResponseData occur Exception when callGet").
                    responseRawContent(responseRawData).build();
        }
        return S2SResponse.<D>builder().httpStatus(httpResponse.getStatusLine().getStatusCode()).
                responseRawContent(responseRawData).responseData(responseData).build();
    }

    public static <A, D> S2SResponse<D> post(String url, A a) {
        return doPost(url, a, null);
    }

    public static <A, D> S2SResponse<D> post(String url, A a, S2SOption s2SOption) {
        return doPost(url, a, s2SOption);
    }

    public static <A, D> S2SResponse<D> postWithData(String url, A a) {
        return doPost(url, a, null);
    }

    public static <A, D> S2SResponse<D> postWithData(String url, A a, S2SOption s2SOption) {
        return doPost(url, a, s2SOption);
    }

    private static <A, D> S2SResponse<D> doPost(String url, A a, S2SOption s2SOption) {
        if (s2SOption == null) {
            s2SOption = S2SOption.builder().build();
        }
        if (s2SOption.getContentType() != null) {
            Map<String, String> headers = s2SOption.getHeaders() == null ? new HashMap<>() : s2SOption.getHeaders();
            headers.put("Content-Type", s2SOption.getContentType().getContent());
            s2SOption.setHeaders(headers);
        }
        long beginTime = System.currentTimeMillis();
        S2SResponse<D> s2SResponse = callPost(url, a, s2SOption);
        if (s2SResponse.getException() != null) {
            s2SResponse.setResponseData(ResponseData.sysError(s2SResponse.getExceptionPoint()));
        }
        s2SResponse.setExecuteTime(System.currentTimeMillis() - beginTime);
        logAccess(METHOD_POST, url, s2SOption, JsonUtil.toJsonStr(a), s2SResponse);
        return s2SResponse;
    }

    public static <T> String callPostReturnString(String url, T req, @NonNull S2SOption s2SOption) {
        if (s2SOption.getContentType() != null) {
            Map<String, String> headers = s2SOption.getHeaders() == null ? new HashMap<>() : s2SOption.getHeaders();
            headers.put("Content-Type", s2SOption.getContentType().getContent());
            s2SOption.setHeaders(headers);
        }
        // 构建URI
        URIBuilder uriBuilder;
        URI uri;
        try {
            uriBuilder = new URIBuilder(url);
            uri = uriBuilder.build();
        } catch (URISyntaxException e) {
            log.error("url invalid", e);
            throw new RuntimeException("uri:" + url + " invalid");
        }
        // 设置参数
        if (s2SOption.getParams() != null && s2SOption.getParams().keySet().size() > 0) {
            for (String key : s2SOption.getParams().keySet()) {
                uriBuilder.setParameter(key, s2SOption.getParams().get(key));
            }
        }
        // 构建HttpGet
        HttpPost httpPost = new HttpPost(uri);
        // 设置头
        if (s2SOption.headerIsNotEmpty()) {
            for (String key : s2SOption.getHeaders().keySet()) {
                httpPost.setHeader(key, s2SOption.getHeaders().get(key));
            }
        }
        // 请求配置：超时等。
        int timeout = s2SOption.getTimeout() > 0 ? s2SOption.getTimeout() : TIMEOUT;
        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(timeout).build();
        httpPost.setConfig(requestConfig);
        // 设置消息体
        if (req != null) {
            String requestBodyStr = JsonUtil.toJsonStr(req);
            StringEntity requestEntity;
            try {
                requestEntity = new StringEntity(requestBodyStr);
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException("new StringEntity occur UnsupportedEncodingException when callPost");
            }
            httpPost.setEntity(requestEntity);
        }
        // 执行请求
        CloseableHttpClient httpClient = HttpClients.custom()
                .addInterceptorFirst(customInterceptor)
                .build();

        CloseableHttpResponse httpResponse;
        try {
            httpResponse = httpClient.execute(httpPost);
        } catch (IOException e) {
            close(httpClient, null);
            throw new RuntimeException("httpClient.execute occur IOException when callPost", e);
        } finally {

        }
        // 获取响应实体
        HttpEntity entity = httpResponse.getEntity();
        String responseRawData;
        try {
            responseRawData = EntityUtils.toString(entity, "utf-8");
        } catch (IOException e) {
            close(httpClient, httpResponse);
            throw new RuntimeException("EntityUtils.toString occur IOException when callPost", e);
        }
        return responseRawData;
    }

    private static <A, D> S2SResponse<D> callPost(String url, A a, S2SOption s2SOption) {
        // 构建URI
        URIBuilder uriBuilder;
        try {
            uriBuilder = new URIBuilder(url);
        } catch (URISyntaxException e) {
            return S2SResponse.<D>builder().httpStatus(-1).
                    exception(e).exceptionPoint("new URIBuilder occur URISyntaxException when callPost").build();
        }
        // 设置参数
        if (s2SOption.getParams() != null && s2SOption.getParams().keySet().size() > 0) {
            for (String key : s2SOption.getParams().keySet()) {
                uriBuilder.setParameter(key, s2SOption.getParams().get(key));
            }
        }
        // 构建HttpGet
        HttpPost httpPost;
        try {
            httpPost = new HttpPost(uriBuilder.build());
        } catch (URISyntaxException e) {
            return S2SResponse.<D>builder().httpStatus(-1).
                    exception(e).exceptionPoint("new HttpPost occur URISyntaxException when callPost").build();
        }
        // 设置头
        if (s2SOption.headerIsNotEmpty()) {
            for (String key : s2SOption.getHeaders().keySet()) {
                httpPost.setHeader(key, s2SOption.getHeaders().get(key));
            }
        }
        // 请求配置：超时等。
        int timeout = s2SOption.getTimeout() > 0 ? s2SOption.getTimeout() : TIMEOUT;
        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(timeout).build();
        httpPost.setConfig(requestConfig);
        // 设置消息体
        String requestBodyStr = null;
        if (a instanceof String) {
            requestBodyStr = (String) a;
        } else {
            requestBodyStr = JsonUtil.toJsonStr(a);
        }
        StringEntity requestEntity;
        try {
            requestEntity = new StringEntity(requestBodyStr);
        } catch (UnsupportedEncodingException e) {
            return S2SResponse.<D>builder().httpStatus(-1).
                    exception(e).exceptionPoint("new StringEntity occur UnsupportedEncodingException when callPost").build();
        }
        httpPost.setEntity(requestEntity);
        // 执行请求

        CloseableHttpClient httpClient = HttpClients.custom()
                .addInterceptorFirst(customInterceptor)
                .build();

        CloseableHttpResponse httpResponse = null;
        try {
            httpResponse = httpClient.execute(httpPost);
        } catch (IOException e) {
            close(httpClient, null);
            return S2SResponse.<D>builder().httpStatus(-1).
                    exception(e).exceptionPoint("httpClient.execute occur IOException when callPost").build();
        }
        // 获取响应实体
        HttpEntity entity = httpResponse.getEntity();
        String responseRawData = "";
        try {
            responseRawData = EntityUtils.toString(entity, "utf-8");
        } catch (IOException e) {
            close(httpClient, httpResponse);
            return S2SResponse.<D>builder().httpStatus(httpResponse.getStatusLine().getStatusCode()).
                    exception(e).exceptionPoint("EntityUtils.toString occur IOException when callPost").build();
        }
        // 反序列化响应体
        ResponseData<D> responseData;
        try {
            responseData = parseResponseData(s2SOption, responseRawData, httpResponse);
        } catch (Exception e) {
            close(httpClient, httpResponse);
            return S2SResponse.<D>builder().httpStatus(httpResponse.getStatusLine().getStatusCode()).
                    exception(e).exceptionPoint("parseResponseData occur Exception when callPost").
                    responseRawContent(responseRawData).build();
        }
        return S2SResponse.<D>builder().httpStatus(httpResponse.getStatusLine().getStatusCode()).
                responseRawContent(responseRawData).responseData(responseData).build();
    }

    private static <D> ResponseData<D> parseResponseData(S2SOption s2SOption, String responseRawData, CloseableHttpResponse httpResponse) {
        if (httpResponse.getStatusLine().getStatusCode() >= 400) {
            return ResponseData.sysError("Http response status code is " + httpResponse.getStatusLine().getStatusCode());
        }
        String contentType = httpResponse.getFirstHeader("Content-Type") == null ? "" : httpResponse.getFirstHeader("Content-Type").getValue();
        contentType = TextUtils.isEmpty(contentType) ? s2SOption.getContentType().getContent() : contentType;
        if (contentType == null || !contentType.toLowerCase().contains("json")) {
            return ResponseData.success((D) responseRawData);
        }
        JsonNode jsonNode = JsonUtil.toTree(responseRawData);
        if (jsonNode.has("status") && jsonNode.get("status").isObject()) {
            // 响应体，{"status":{"code":1, "msg":"xxxxx"}, "data":任何类型}
            return JsonUtil.toObject(responseRawData, new TypeReference<ResponseData<D>>() {
            });
        } else if (jsonNode.has("code") && jsonNode.has("data")) {
            // 响应体，{"code":1, "msg":"xxx", data:任何类型}  或者 响应体，{"code":"0000", "msg":"xxx", data:任何类型}
            int code = Integer.valueOf(jsonNode.get("code").toString());
            String msg = jsonNode.has("msg") ? jsonNode.get("msg").toString() : "";
            String err = jsonNode.has("err") ? jsonNode.get("err").toString() : "";
            ResponseData responseData = JsonUtil.toObject(responseRawData, new TypeReference<ResponseData<D>>() {
            });
            responseData.setStatus(new Status(code, msg, err));
            return responseData;
        } else {
            // 响应体，{"code":1, ...} 或者 响应体，{...}
            int code = jsonNode.has("code") ? Integer.valueOf(jsonNode.get("code").toString()) : 0;
            String msg = jsonNode.has("msg") ? jsonNode.get("msg").toString() : "";
            String err = jsonNode.has("err") ? jsonNode.get("err").toString() : "";
            D data = JsonUtil.toObject(responseRawData, new TypeReference<D>() {
            });
            return new ResponseData<>(new Status(code, msg, err), data);
        }
    }

    private static void close(CloseableHttpClient httpClient, CloseableHttpResponse httpResponse) {
        if (httpResponse != null) {
            try {
                httpResponse.close();
            } catch (IOException e) {
                log.error("", e);
            }
        }
        if (httpClient != null) {
            try {
                httpClient.close();
            } catch (IOException e) {
                log.error("", e);
            }
        }
    }

    private static <D> void logAccess(String requestMethod, String url, S2SOption s2SOption, String requestBody, S2SResponse<D> s2SResponse) {
        Map<String, String> params = s2SOption.getParams() == null ? new HashMap<>() : s2SOption.getParams();
        Map<String, String> headers = s2SOption.getHeaders() == null ? new HashMap<>() : s2SOption.getHeaders();
        String requestId = WebThreadContext.getRequestId() == null ? "" : WebThreadContext.getRequestId();
        Object[] objs = new Object[]{requestMethod, url, s2SResponse.getHttpStatus(), s2SResponse.getResponseData().getStatus().getCode(),
                s2SResponse.getExecuteTime(), requestId, headers, params, requestBody,
                s2SResponse.getResponseRawContent(), JsonUtil.toJsonStr(s2SResponse.getResponseData())};
        String logFormat = "[S2SHttp-{}] url={} httpCode={} code={} executeTime={} requestId={} header={} params={} requestBody={} responseRawContent={} responseData={}";
        if (s2SResponse.getResponseData().isSuccess() ||
                (s2SOption.isIgnoreErrCode(s2SResponse.getResponseData().getStatus().getCode()))) {
            log.info(logFormat, objs);
        } else {
            log.error(logFormat, objs);
        }
    }

}